package org.dfl.plugin.editor.model;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.Collection;

import org.dfl.core.model.IOElement;
import org.dfl.plugin.editor.markers.ModelMarkersContainer;
import org.dfl.plugin.editor.markers.ModelProblem;
import org.eclipse.core.resources.IResource;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.views.properties.IPropertyDescriptor;
import org.eclipse.ui.views.properties.IPropertySource;


/**
 * Abstract prototype of a model element.
 * <p>
 * This class provides features necessary for all model elements, like:
 * </p>
 * <ul>
 * <li>property-change support (used to notify edit parts of model changes),</li>
 * <li>property-source support (used to display property values in the Property
 * View) and</li>
 * <li>serialization support (the model hierarchy must be serializable, so that
 * the editor can save and restore a binary representation. You might not need
 * this, if you store the model a non-binary form like XML).</li>
 * </ul>
 * 
 * @author Piotr Wlodarczyk
 */
public abstract class ModelElement implements IPropertySource, IOElement  {
	// plik, ktory jest edytowany 
	private DflDiagram diagram = null;	
	private ModelMarkersContainer problemsContainer = null;
	
	/** An empty property descriptor. */
	private static final IPropertyDescriptor[] EMPTY_ARRAY = new IPropertyDescriptor[0];

	private static final long serialVersionUID = 1;

	/** Delegate used to implemenent property-change-support. */
	private transient PropertyChangeSupport pcsDelegate = new PropertyChangeSupport(this);
	private boolean refreshDiagramEnabled = true;
	
	/**
	 * Attach a non-null PropertyChangeListener to this object.
	 * 
	 * @param l
	 *            a non-null PropertyChangeListener instance
	 * @throws IllegalArgumentException
	 *             if the parameter is null
	 */
	public void addPropertyChangeListener(PropertyChangeListener l) {
		if (l == null) {
			throw new IllegalArgumentException();
		}
		pcsDelegate.addPropertyChangeListener(l);
	}

	public boolean isRefreshDiagramEnabled() {
		return refreshDiagramEnabled;
	}

	public void setRefreshDiagramEnabled(boolean refreshDiagramEnabled) {
		this.refreshDiagramEnabled = refreshDiagramEnabled;
	}
	
	/**
	 * Report a property change to registered listeners (for example edit
	 * parts).
	 * 
	 * @param property
	 *            the programmatic name of the property that changed
	 * @param oldValue
	 *            the old value of this property
	 * @param newValue
	 *            the new value of this property
	 */
	protected void firePropertyChange(final String property,final Object oldValue,
			final Object newValue) {
		if (isRefreshDiagramEnabled() && pcsDelegate.hasListeners(property)) {
			Runnable r = new Runnable() {
				public void run() {
					pcsDelegate.firePropertyChange(property, oldValue, newValue);
				}
			};

			if (Display.getCurrent() != null) {
				r.run();
			} else {
				Display.getDefault().asyncExec(r); // notice aSYNC exec
			}
		}
	}

	/**
	 * Returns a value for this property source that can be edited in a property
	 * sheet.
	 * <p>
	 * My personal rule of thumb:
	 * </p>
	 * <ul>
	 * <li>model elements should return themselves and</li>
	 * <li>custom IPropertySource implementations (like DimensionPropertySource
	 * in the GEF-logic example) should return an editable value.</li>
	 * </ul>
	 * <p>
	 * Override only if necessary.
	 * </p>
	 * 
	 * @return this instance
	 */
	public Object getEditableValue() {
		return this;
	}

	/**
	 * Children should override this. The default implementation returns an
	 * empty array.
	 */
	public IPropertyDescriptor[] getPropertyDescriptors() {
		return EMPTY_ARRAY;
	}

	/**
	 * Children should override this. The default implementation returns null.
	 */
	public Object getPropertyValue(Object id) {
		return null;
	}

	/**
	 * Children should override this. The default implementation returns false.
	 */
	public boolean isPropertySet(Object id) {
		return false;
	}

	/**
	 * Remove a PropertyChangeListener from this component.
	 * 
	 * @param l
	 *            a PropertyChangeListener instance
	 */
	public synchronized void removePropertyChangeListener(
			PropertyChangeListener l) {
		if (l != null) {
			pcsDelegate.removePropertyChangeListener(l);
		}
	}

	/**
	 * Children should override this. The default implementation does nothing.
	 */
	public void resetPropertyValue(Object id) {
		// do nothing
	}

	/**
	 * Children should override this. The default implementation does nothing.
	 */
	public void setPropertyValue(Object id, Object value) {
		// do nothing
	}

	public DflDiagram getDiagram() {
		return diagram;
	}

	/**
	 * kazdy element modely powinien trzymac link do zrodla, do ktorego nalezy - ustawiane przy wczytywaniu
	 * modelu i przy dodawaniu nowych elementow modelu
	 * @param diagramResource
	 */
	public void setDiagram(DflDiagram diagram) {
		this.diagram = diagram;
	}
	
	
	public void resetMarkers(boolean unregister){
		// clear old markers
		if(this.problemsContainer!=null) problemsContainer.clearMarkers();
		
		//every time the diagramResourse is changed, the problemsContianer - markers
		//has to be refreshed - if the modelElement is removed (diagramResource==null)
		//then we have to unregister the contianer and set it to null 
		if(unregister || getDiagram()==null){
			if(this.problemsContainer!=null) this.problemsContainer.unregister();
			this.problemsContainer = null;
		}else{
			this.problemsContainer = getNewProblemsContainer(diagram.getDiagramResource());
			refreshMarkers();
		}
	}

	protected ModelMarkersContainer getNewProblemsContainer(IResource diagramResource){
		if (diagramResource == null) {
			return null;
		} else {
			return new ModelMarkersContainer(this, diagramResource);
		}
	}
	
	public void refreshMarkers(){
		if(isRefreshDiagramEnabled() && problemsContainer != null ) {
			problemsContainer.rebuildMarkers();
		}
	}
	
	// domyslnie nie ma problemow - trzeba przedziedziczyc
	public Collection<ModelProblem> getProblems(){
		return new ArrayList<ModelProblem>();
	}
	
	public void focus(){
		if(getDiagram()!=null){
			getDiagram().focus(this);
		}
	}
	
}
